-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Fix hydration error #643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix hydration error #643
Conversation
👷 Deploy request for appcut pending review.Visit the deploys page to approve it
|
|
@SamSyntax is attempting to deploy a commit to the OpenCut OSS Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughReplaces the app ThemeProvider with a new hydration-safe CustomThemeProvider, adds the CustomThemeProvider implementation that defers applying ThemeProvider until client mount, and updates the theme toggle to render Sun or Moon based on current theme. Changes
Sequence DiagramsequenceDiagram
participant Browser as Browser (SSR/CSR)
participant Layout as layout.tsx
participant Custom as CustomThemeProvider
participant Theme as ThemeProvider
Browser->>Layout: Receive server-rendered HTML
Layout->>Custom: Render CustomThemeProvider (mounted=false)
Custom-->>Browser: Return children only (no ThemeProvider) to match SSR
Browser->>Custom: Client mount triggers useEffect
Custom->>Custom: set mounted = true
Custom->>Theme: Render ThemeProvider with provided props + children
Theme-->>Browser: Theme context applied on client
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/app/layout.tsx (1)
38-58: Refactor component tree to ensureuseTheme()is only called withinThemeProvidercontext.The concern is valid and confirmed.
CustomThemeProviderreturns unwrapped children before mounting (line 16-17 in theme-provider.tsx), butToasterusesuseTheme()and renders unconditionally within that tree. This causesuseTheme()to be called outside its provider context during the initial render, violating hook usage rules and potentially causing runtime warnings or errors.Move
ToasterandAnalyticsinsideStorageProvider, or conditionally render them only after the provider mounts:{mounted && ( <> <Analytics /> <Toaster /> </> )}Alternatively, refactor
CustomThemeProviderto wrap the entire children tree with a loading boundary untilmountedis true.
🧹 Nitpick comments (2)
apps/web/src/components/theme-provider.tsx (2)
6-8: Consider removing redundantchildrendeclaration.
ThemeProviderPropsfromnext-themeslikely already includeschildren: React.ReactNode, making this explicit declaration unnecessary.-interface CustomThemeProviderProps extends ThemeProviderProps { - children: React.ReactNode; -} +type CustomThemeProviderProps = ThemeProviderProps;
20-20: Remove extra whitespace in closing tag.There's an extra space before the closing
>in</ThemeProvider >.- return <ThemeProvider {...props}>{children}</ThemeProvider > + return <ThemeProvider {...props}>{children}</ThemeProvider>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/src/app/layout.tsx(3 hunks)apps/web/src/components/theme-provider.tsx(1 hunks)apps/web/src/components/theme-toggle.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{jsx,tsx}: Don't useaccessKeyattribute on any HTML element.
Don't setaria-hidden="true"on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Don't use distracting elements like<marquee>or<blink>.
Only use thescopeprop on<th>elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assigntabIndexto non-interactive HTML elements.
Don't use positive integers fortabIndexproperty.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include atitleelement for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
AssigntabIndexto non-interactive HTML elements witharia-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include atypeattribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden).
Always include alangattribute on the html element.
Always include atitleattribute for iframe elements.
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress.
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur.
Include caption tracks for audio and video elements.
Use semantic elements instead of role attributes in JSX.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with...
Files:
apps/web/src/components/theme-provider.tsxapps/web/src/app/layout.tsxapps/web/src/components/theme-toggle.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use TypeScript namespaces.
Don't use non-null assertions with the!postfix operator.
Don't use parameter properties in class constructors.
Don't use user-defined types.
Useas constinstead of literal types and type annotations.
Use eitherT[]orArray<T>consistently.
Initialize each enum member value explicitly.
Useexport typefor types.
Useimport typefor types.
Make sure all enum members are literal values.
Don't use TypeScript const enum.
Don't declare empty interfaces.
Don't let variables evolve into any type through reassignments.
Don't use the any type.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use implicit any type on variable declarations.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use the TypeScript directive @ts-ignore.
Use consistent accessibility modifiers on class properties and methods.
Use function types instead of object types with call signatures.
Don't use void type outside of generic or return types.
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
Use either T[] or Array consistently
Don't use the any type
Files:
apps/web/src/components/theme-provider.tsxapps/web/src/app/layout.tsxapps/web/src/components/theme-toggle.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,ts,tsx}: Don't use the return value of React.render.
Don't use consecutive spaces in regular expression literals.
Don't use theargumentsobject.
Don't use primitive type aliases or misleading types.
Don't use the comma operator.
Don't write functions that exceed a given Cognitive Complexity score.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use uselessthisaliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names that aren't base 10 or use underscore separators.
Remove redundant terms from logical expressions.
Use while loops instead of...
Files:
apps/web/src/components/theme-provider.tsxapps/web/src/app/layout.tsxapps/web/src/components/theme-toggle.tsx
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{tsx,jsx}: Always include a title element for icons unless there's text beside the icon
Always include a type attribute for button elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Don't import React itself
Don't define React components inside other components
Don't use both children and dangerouslySetInnerHTML on the same element
Don't insert comments as text nodes
Use <>...</> instead of ...
Files:
apps/web/src/components/theme-provider.tsxapps/web/src/app/layout.tsxapps/web/src/components/theme-toggle.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{ts,tsx,js,jsx}: Don't use the comma operator
Use for...of statements instead of Array.forEach
Don't initialize variables to undefined
Use .flatMap() instead of map().flat() when possible
Don't assign a value to itself
Avoid unused imports and variables
Don't use await inside loops
Don't hardcode sensitive data like API keys and tokens
Don't use the delete operator
Don't use global eval()
Use String.slice() instead of String.substr() and String.substring()
Don't use else blocks when the if block breaks early
Put default function parameters and optional function parameters last
Use new when throwing an error
Use String.trimStart() and String.trimEnd() over String.trimLeft() and String.trimRight()
Files:
apps/web/src/components/theme-provider.tsxapps/web/src/app/layout.tsxapps/web/src/components/theme-toggle.tsx
🧠 Learnings (4)
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Use semantic elements instead of role attributes in JSX.
Applied to files:
apps/web/src/app/layout.tsx
📚 Learning: 2025-08-15T00:50:09.126Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-08-15T00:50:09.126Z
Learning: Applies to **/*.{tsx,jsx} : Use <>...</> instead of <Fragment>...</Fragment>
Applied to files:
apps/web/src/app/layout.tsx
📚 Learning: 2025-08-15T00:50:09.126Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-08-15T00:50:09.126Z
Learning: Applies to **/*.{tsx,jsx} : Always include a title element for icons unless there's text beside the icon
Applied to files:
apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{ts,tsx} : Use consistent accessibility modifiers on class properties and methods.
Applied to files:
apps/web/src/components/theme-toggle.tsx
🧬 Code graph analysis (2)
apps/web/src/components/theme-provider.tsx (1)
apps/web/src/components/ui/sonner.tsx (1)
props(8-29)
apps/web/src/app/layout.tsx (1)
apps/web/src/components/theme-provider.tsx (1)
CustomThemeProvider(10-21)
🔇 Additional comments (2)
apps/web/src/app/layout.tsx (1)
12-12: LGTM: Clean import swap for custom wrapper.The import correctly replaces the direct
next-themesThemeProvider with the new hydration-safe wrapper.apps/web/src/components/theme-provider.tsx (1)
1-21: I need to verify a few more things about the actual implementation and what hydration errors are being addressed.Now let me verify what's actually in the layout file:
No issues found. The implementation correctly follows next-themes documentation.
The next-themes library explicitly recommends delaying rendering any theme toggling UI until mounted on the client, and this component implements exactly that pattern. When using useTheme, a hydration mismatch error occurs because values returned by useTheme are undefined on the server, since localStorage cannot be read until mounting on the client.
The mounted check with
useEffectis the standard recommended approach, not a workaround. The component correctly renders children without the ThemeProvider during SSR and wraps them only after client-side hydration completes.
| export default function CustomThemeProvider({ children, ...props }: CustomThemeProviderProps) { | ||
| const [mounted, setMounted] = useState(false); | ||
| useEffect(() => { | ||
| setMounted(true); | ||
| }, []); | ||
|
|
||
| if (!mounted) { | ||
| return <>{children}</>; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Missing ThemeProvider context causes runtime errors for child components.
Returning <>{children}</> without the ThemeProvider wrapper when !mounted (lines 16-18) breaks the theme context. Components like Toaster (which calls useTheme() as shown in the relevant snippet from sonner.tsx) will throw an error during the initial render: "useTheme must be used within a ThemeProvider".
The fix for hydration errors should keep the ThemeProvider wrapped at all times. The suppressHydrationWarning attribute on the <html> tag (already present in layout.tsx line 33) handles theme-related hydration mismatches.
Apply this diff to fix the context issue:
export default function CustomThemeProvider({ children, ...props }: CustomThemeProviderProps) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
- if (!mounted) {
- return <>{children}</>;
- }
-
- return <ThemeProvider {...props}>{children}</ThemeProvider >
+ return (
+ <ThemeProvider {...props}>
+ {mounted ? children : null}
+ </ThemeProvider>
+ );
}Alternatively, if you want to avoid the layout shift from returning null, simply always render children within ThemeProvider and rely on suppressHydrationWarning:
export default function CustomThemeProvider({ children, ...props }: CustomThemeProviderProps) {
- const [mounted, setMounted] = useState(false);
- useEffect(() => {
- setMounted(true);
- }, []);
-
- if (!mounted) {
- return <>{children}</>;
- }
-
- return <ThemeProvider {...props}>{children}</ThemeProvider >
+ return <ThemeProvider {...props}>{children}</ThemeProvider>;
}The second approach is simpler and leverages Next.js's built-in hydration handling via suppressHydrationWarning.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/web/src/components/theme-provider.tsx around lines 10 to 18, the
component currently returns <>children</> when !mounted which removes the
ThemeProvider and causes runtime errors (e.g., "useTheme must be used within a
ThemeProvider"); remove the early return and always render the ThemeProvider
wrapper around children (i.e., render ThemeProvider regardless of mounted state)
to preserve theme context during initial render and rely on the existing
suppressHydrationWarning in layout.tsx to handle hydration mismatches.
Fix typo in classname
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/components/theme-toggle.tsx (1)
15-25: Add explicittype="button"to the Button element.The custom
Buttoncomponent does not provide a defaulttypeattribute. When rendered as a native button element (the default), it will inherit the browser's default type of"submit", which can cause unintended form submissions. While this button isn't in a form context here, adding an explicittype="button"follows the coding guidelines and prevents potential issues if the component is reused elsewhere.<Button size="icon" variant="text" className="h-7" + type="button" onClick={() => setTheme(theme === "dark" ? "light" : "dark")} >
🧹 Nitpick comments (1)
apps/web/src/components/theme-toggle.tsx (1)
28-29: Remove extra trailing newline.The file has two trailing newlines. Consider keeping only one for consistency with common formatting conventions.
Apply this diff:
); } -
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/components/theme-toggle.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{jsx,tsx}: Don't useaccessKeyattribute on any HTML element.
Don't setaria-hidden="true"on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Don't use distracting elements like<marquee>or<blink>.
Only use thescopeprop on<th>elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assigntabIndexto non-interactive HTML elements.
Don't use positive integers fortabIndexproperty.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include atitleelement for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
AssigntabIndexto non-interactive HTML elements witharia-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include atypeattribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden).
Always include alangattribute on the html element.
Always include atitleattribute for iframe elements.
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress.
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur.
Include caption tracks for audio and video elements.
Use semantic elements instead of role attributes in JSX.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with...
Files:
apps/web/src/components/theme-toggle.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use TypeScript namespaces.
Don't use non-null assertions with the!postfix operator.
Don't use parameter properties in class constructors.
Don't use user-defined types.
Useas constinstead of literal types and type annotations.
Use eitherT[]orArray<T>consistently.
Initialize each enum member value explicitly.
Useexport typefor types.
Useimport typefor types.
Make sure all enum members are literal values.
Don't use TypeScript const enum.
Don't declare empty interfaces.
Don't let variables evolve into any type through reassignments.
Don't use the any type.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use implicit any type on variable declarations.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use the TypeScript directive @ts-ignore.
Use consistent accessibility modifiers on class properties and methods.
Use function types instead of object types with call signatures.
Don't use void type outside of generic or return types.
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
Use either T[] or Array consistently
Don't use the any type
Files:
apps/web/src/components/theme-toggle.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,ts,tsx}: Don't use the return value of React.render.
Don't use consecutive spaces in regular expression literals.
Don't use theargumentsobject.
Don't use primitive type aliases or misleading types.
Don't use the comma operator.
Don't write functions that exceed a given Cognitive Complexity score.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use uselessthisaliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names that aren't base 10 or use underscore separators.
Remove redundant terms from logical expressions.
Use while loops instead of...
Files:
apps/web/src/components/theme-toggle.tsx
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{tsx,jsx}: Always include a title element for icons unless there's text beside the icon
Always include a type attribute for button elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Don't import React itself
Don't define React components inside other components
Don't use both children and dangerouslySetInnerHTML on the same element
Don't insert comments as text nodes
Use <>...</> instead of ...
Files:
apps/web/src/components/theme-toggle.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{ts,tsx,js,jsx}: Don't use the comma operator
Use for...of statements instead of Array.forEach
Don't initialize variables to undefined
Use .flatMap() instead of map().flat() when possible
Don't assign a value to itself
Avoid unused imports and variables
Don't use await inside loops
Don't hardcode sensitive data like API keys and tokens
Don't use the delete operator
Don't use global eval()
Use String.slice() instead of String.substr() and String.substring()
Don't use else blocks when the if block breaks early
Put default function parameters and optional function parameters last
Use new when throwing an error
Use String.trimStart() and String.trimEnd() over String.trimLeft() and String.trimRight()
Files:
apps/web/src/components/theme-toggle.tsx
🧠 Learnings (6)
📚 Learning: 2025-08-15T00:50:09.126Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-08-15T00:50:09.126Z
Learning: Applies to **/*.{tsx,jsx} : Always include a title element for icons unless there's text beside the icon
Applied to files:
apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Watch out for possible "wrong" semicolons inside JSX elements.
Applied to files:
apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use constants whose value is the upper-case version of their name.
Applied to files:
apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use standard constants instead of approximated literals.
Applied to files:
apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{ts,tsx} : Don't misuse the non-null assertion operator (!) in TypeScript files.
Applied to files:
apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use duplicate class members.
Applied to files:
apps/web/src/components/theme-toggle.tsx
🧬 Code graph analysis (1)
apps/web/src/components/theme-toggle.tsx (1)
apps/web/src/components/ui/sonner.tsx (1)
props(8-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Vercel Agent Review
| {theme === "dark" ? ( | ||
| <Sun className="!size-[1.1rem]" /> | ||
| ) : (<Moon className="!size-[1.1rem]" />)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Potential hydration mismatch with undefined theme.
The theme value from useTheme() may be undefined during SSR or the first client render (before the CustomThemeProvider mounts), causing the conditional on line 21 to render the Moon icon. Once theme resolves on the client, it might switch to Sun if the theme is "dark", creating a hydration mismatch.
Consider guarding against undefined theme or deferring the render until theme is available.
Apply this diff to add a guard:
- {theme === "dark" ? (
+ {theme === "dark" ? (
<Sun className="!size-[1.1rem]" />
- ) : (<Moon className="!size-[1.1rem]" />)}
+ ) : theme === "light" ? (
+ <Moon className="!size-[1.1rem]" />
+ ) : (
+ <Sun className="!size-[1.1rem]" />
+ )}Alternatively, consider not rendering any icon until theme is defined:
- {theme === "dark" ? (
- <Sun className="!size-[1.1rem]" />
- ) : (<Moon className="!size-[1.1rem]" />)}
+ {theme === "dark" ? (
+ <Sun className="!size-[1.1rem]" />
+ ) : theme === "light" ? (
+ <Moon className="!size-[1.1rem]" />
+ ) : null}Run the following script to check if the CustomThemeProvider properly prevents hydration mismatches:
🏁 Script executed:
#!/bin/bash
# Description: Check CustomThemeProvider implementation and ThemeToggle usage
# Check CustomThemeProvider implementation for mounted state
echo "=== CustomThemeProvider implementation ==="
rg -A 20 'export.*CustomThemeProvider' --type tsx
# Check if ThemeToggle guards against undefined theme
echo -e "\n=== ThemeToggle usage of theme ==="
rg -B 2 -A 2 'theme ===' apps/web/src/components/theme-toggle.tsxLength of output: 448
🏁 Script executed:
#!/bin/bash
# Better search for CustomThemeProvider without problematic type flag
echo "=== CustomThemeProvider implementation ==="
rg -A 20 'export.*CustomThemeProvider'
# Also check the full theme-toggle.tsx file to see all code
echo -e "\n=== Full theme-toggle.tsx file ==="
cat apps/web/src/components/theme-toggle.tsx
# Check if there's a hooks file or provider file
echo -e "\n=== Looking for theme-related files ==="
fd -e tsx -e ts | grep -i theme | head -20Length of output: 1736
Address potential hydration mismatch in conditional theme rendering.
The component renders different icons based on the theme value without guarding against undefined. During SSR and before the CustomThemeProvider mounts on the client, useTheme() returns theme: undefined, causing the conditional to render the Moon icon. Once the provider mounts and theme resolves (e.g., to "dark"), the component attempts to render the Sun icon instead, creating a hydration mismatch.
Add a guard to handle the undefined state:
- {theme === "dark" ? (
+ {theme === "dark" ? (
<Sun className="!size-[1.1rem]" />
- ) : (<Moon className="!size-[1.1rem]" />)}
+ ) : theme === "light" ? (
+ <Moon className="!size-[1.1rem]" />
+ ) : null}Or ensure the component doesn't render until theme is available by checking it within a useEffect or wrapping with a loading state.
🤖 Prompt for AI Agents
In apps/web/src/components/theme-toggle.tsx around lines 21 to 23, the
conditional renders icons directly from theme which can be undefined during SSR
causing hydration mismatches; update the component to guard against undefined by
checking theme before rendering (e.g., return null or a placeholder while theme
=== undefined) or use a mounted flag (set true in useEffect) and only render the
Sun/Moon once theme is defined, ensuring the same markup is produced on server
and client to avoid hydration errors.
Description
Please include a summary of the changes and the related issue. Please also include relevant motivation and context.
Fixes #629 #635 #621
This is a really tiny modification that fixes hydration error mentioned in #629, #635 and #621
CustomThemeProvider just wraps the ThemeProvider from next-themes and uses useState to set mounted state as false initially and then calls useEffect that runs only once on the initial load to set mounted to true. Since it's a client-side hook, it does not run on server so we receive a neutral html from server without theme mismatch. I believe this is a common pattern in working with next-themes, but if anyone knows a better way to handle that I would love to know!
This pr also adds the theme icon change on toggle.
Type of change
How Has This Been Tested?
This change has been tested on on both Chromium browser and Zen browser which is using firefox engine under the hood.
Test Configuration:
Screenshots (if applicable)
Checklist:
Summary by CodeRabbit
New Features
Bug Fixes
Style